#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2022 Huaqin Technology. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#    * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#    * Neither the name of Huaqin Technology nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import subprocess
import argparse
import os
import time
import datetime
import threading
import re
import sys
import csv
import json
import pprint
import signal
import xml.etree.ElementTree as XMLET
import zipfile

import map_analyzer.map_analyzer as map_analyzer

_TOOLS_PATH = os.getcwd() + '/'
_WORKING_ROOT_DIR = 'working/'
_WORKING_ROOT_PATH = os.getcwd() + '/' + _WORKING_ROOT_DIR

_PROJECT_LIST_tws_master_202208301600 = ["TS6569A", "TS6656A"]
_PROJECT_LIST_tws_ts6569a_202302141700 = ["TS6569A_STR4"]
_PROJECT_LIST_tws_hw_baseline_202301041100 = ["TS6653A"]

_PROJECT_CONFIG_INFO = {
    # 柯西
    'TS6569A' : {
        'platform_type': 'bes',
        'code_server': 'gerritslavesh.huaqin.com',
        'code_server_port': '29418',
        'cherrypick_server': 'gerrit.huaqin.com',
        'cherrypick_server_port': '29418',
        'review_server': 'gerrit.huaqin.com',
        'review_server_port': '29418',
        'manifest': 'BD1SW/tws_master_202208301600.xml',
        'earphone_build_cmd': './build.sh ts6569a',
        'earphone_build_dir': 'earphone/',
        'earphone_build_test_file': 'earphone/build.sh',
        'earphone_build_output_path': 'earphone/out/',
        'earphone_build_output_file_path': 'earphone/out/ts6569a/user/',
        'earphone_build_output_file_list': ['_best1000.lds',
                                            'ts6569a.bin',
                                            'ts6569a.elf',
                                            'ts6569a.lst',
                                            'ts6569a.map',
                                            'ts6569a.str'],
        'img_file_dict': {'earbud': {'map': 'ts6569a.map',
                                       'bin': 'ts6569a.bin'}},
        'twslint_project': ['HQ_BD1SW_PA1952/earphone'],
        'cppcheck_project': ['HQ_BD1SW_PA1952/earphone'],
        "impact_project_list": _PROJECT_LIST_tws_master_202208301600,
        # 'res_check_project': {'HQ_BD1SW_PA1952/earphone'},
        # 'xml': 'BD1SW/tws_master_202208301600.xml'
    },
    # 柯西 STR4A分支
    'TS6569A_STR4' : {
        'platform_type': 'bes',
        'code_server': 'gerritslavesh.huaqin.com',
        'code_server_port': '29418',
        'cherrypick_server': 'gerrit.huaqin.com',
        'cherrypick_server_port': '29418',
        'review_server': 'gerrit.huaqin.com',
        'review_server_port': '29418',
        'manifest': 'BD1SW/tws_ts6569a_202302141700.xml',
        'earphone_build_cmd': './build.sh ts6569a',
        'earphone_build_dir': 'earphone/',
        'earphone_build_test_file': 'earphone/build.sh',
        'earphone_build_output_path': 'earphone/out/',
        'earphone_build_output_file_path': 'earphone/out/ts6569a/user/',
        'earphone_build_output_file_list': ['_best1000.lds',
                                            'ts6569a.bin',
                                            'ts6569a.elf',
                                            'ts6569a.lst',
                                            'ts6569a.map',
                                            'ts6569a.str'],
        'img_file_dict': {'earbud': {'map': 'ts6569a.map',
                                       'bin': 'ts6569a.bin'}},
        'twslint_project': ['HQ_BD1SW_PA1952/earphone'],
        'cppcheck_project': ['HQ_BD1SW_PA1952/earphone'],
        "impact_project_list": _PROJECT_LIST_tws_ts6569a_202302141700,
        # 'res_check_project': {'HQ_BD1SW_PA1952/earphone'},
        # 'xml': 'BD1SW/tws_master_202208301600.xml'
    },
    # 天燕
    'TS6656A' : {
        'platform_type': 'bes',
        'code_server': 'gerritslavesh.huaqin.com',
        'code_server_port': '29418',
        'cherrypick_server': 'gerrit.huaqin.com',
        'cherrypick_server_port': '29418',
        'review_server': 'gerrit.huaqin.com',
        'review_server_port': '29418',
        'manifest': 'BD1SW/tws_master_202208301600.xml',
        'earphone_build_cmd': './build.sh ts6656a',
        'earphone_build_dir': 'earphone/',
        'earphone_build_test_file': 'earphone/build.sh',
        'earphone_build_output_path': 'earphone/out/',
        'earphone_build_output_file_path': 'earphone/out/ts6656a/user/',
        'earphone_build_output_file_list': ['_best1000.lds',
                                            'ts6656a.bin',
                                            'ts6656a.elf',
                                            'ts6656a.lst',
                                            'ts6656a.map',
                                            'ts6656a.str'],
        'img_file_dict': {'earbud': {'map': 'ts6656a.map',
                                       'bin': 'ts6656a.bin'}},
        'twslint_project': ['HQ_BD1SW_PA1952/earphone'],
        'cppcheck_project': ['HQ_BD1SW_PA1952/earphone'],
        "impact_project_list": _PROJECT_LIST_tws_master_202208301600,
        # 'res_check_project': {'HQ_BD1SW_PA1952/earphone'},
        # 'xml': 'BD1SW/tws_master_202208301600.xml'
    },
    # ULC
    'TS6653A' : {
        'platform_type': 'wq',
        'code_server': 'gerritslavesh.huaqin.com',
        'code_server_port': '29418',
        'cherrypick_server': 'gerrit.huaqin.com',
        'cherrypick_server_port': '29418',
        'review_server': 'gerrit.huaqin.com',
        'review_server_port': '29418',
        'manifest': 'BD1SW/tws_hw_baseline_202301041100.xml',
        'earphone_build_cmd': './build.sh ts6653a',
        'earphone_build_dir': 'earphone/',
        'earphone_build_test_file': 'earphone/build.sh',
        'earphone_build_output_path': 'earphone/out/',
        'earphone_build_output_file_path': 'earphone/out/ts6653a/bin/',
        'earphone_build_output_file_list': ["tws_core0.asm",
                                            "tws_core0.txt",
                                            "link.lds",
                                            "tws_core0.bin",
                                            "tws_core0.info",
                                            "log_table.txt",
                                            "dbglog_table.txt",
                                            "tws_core0.log",
                                            "memory_config.json",
                                            "tws_core0.elf",
                                            "tws_core0.map",
                                            "../package/*.wpk"],
        'img_file_dict': {'earbud': {'map': 'tws_core0.map',
                                       'bin': 'tws_core0.bin',
                                       'layout': 'memory_config.json',
                                       "pkg": "patch_tws_2_0-ts6653a-0.0.0.0_offline_log.wpk"}},
        'twslint_project': ['HQ_BD1SW_PA1952/earphone'],
        'cppcheck_project': ['HQ_BD1SW_PA1952/earphone'],
        "impact_project_list": _PROJECT_LIST_tws_hw_baseline_202301041100,
        "check_safe_function": True
        # 'res_check_project': {'HQ_BD1SW_PA1952/earphone'},
        # 'xml': 'BD1SW/tws_hw_baseline_202301041100.xml'
    },
}

# account_id, code_server, code_server_port, manifest
_CODE_PULL_CMD_TEMPLATE = 'repo init --no-repo-verify -u ssh://%s@%s:%s/manifest -m %s  --depth=1 && repo sync -j4 -c --no-tags && repo start dev --all'

# account_id, cherrypick_server, cherrypick_server_port, _gerrit_project, _gerrit_ref_info
# _CHERRYPICK_CMD_TEMPLATE = 'git fetch ssh://101002563@gerrit.huaqin.com:29418/HQ_BD1SW_PA1952/earphone refs/changes/78/256878/1 && git cherry-pick FETCH_HEAD'
_CHERRYPICK_CMD_TEMPLATE = 'git fetch ssh://%s@%s:%s/%s %s && git cherry-pick FETCH_HEAD'

# review_server_port, account_id, review_server, review_message, num, patchset
# ssh -p 29418 101002563@gerrit.huaqin.com gerrit review -m \"Build Successful.\" 256901,2
_GERRIT_REVIEW_CMD_TEMPLATE = 'ssh -p %s %s@%s gerrit review -m %s %s,%s'

LEVEL_ERROR = '[Error]'
LEVEL_INFO = '[Info]'
LEVEL_WARN = '[Warning]'

_LOG_ENV_WORKING_DIR = ''
_LOG_ENV_CODE_DIR = ''
_LOG_ENV_GUARD_INFO_HANDLE = None
def _LogInit(workdir='', codedir='', guard_info_handle=None):
    global _LOG_ENV_WORKING_DIR
    global _LOG_ENV_CODE_DIR
    global _LOG_ENV_GUARD_INFO_HANDLE
    if workdir:
        _LOG_ENV_WORKING_DIR = workdir
    if codedir:
        _LOG_ENV_CODE_DIR = codedir
    if guard_info_handle:
        _LOG_ENV_GUARD_INFO_HANDLE = guard_info_handle
    return


def LOG(level, msg):
    def time_str():
        return time.strftime('%Y-%m-%d %H:%M:%S  ', time.localtime())
    log_msg = time_str() + level + ': ' + str(msg)
    print(log_msg)
    if level == LEVEL_ERROR:
        if os.path.exists(_LOG_ENV_CODE_DIR):
            cmd = 'rm -rf %s' % (_LOG_ENV_CODE_DIR)
            # subprocess.run(cmd, shell=True, timeout=120, encoding='utf8')
            TIMEOUT_SHELL_COMMAND(cmd, timeout=120)

        if _LOG_ENV_GUARD_INFO_HANDLE:
            port = _PROJECT_CONFIG_INFO.get(_LOG_ENV_GUARD_INFO_HANDLE._project_name).get('review_server_port')
            account = _LOG_ENV_GUARD_INFO_HANDLE.account_id
            server = _PROJECT_CONFIG_INFO.get(_LOG_ENV_GUARD_INFO_HANDLE._project_name).get('review_server')
            msg = '[TWS_GUARD] ' + "Review ***FAIL*** at {}\\\n\\\r".format(_LOG_ENV_GUARD_INFO_HANDLE.jenkins_url)
            for index in range(0, _LOG_ENV_GUARD_INFO_HANDLE._gerrit_project_count):
                change_num = _LOG_ENV_GUARD_INFO_HANDLE._gerrit_number[index]
                patch_set = _LOG_ENV_GUARD_INFO_HANDLE._gerrit_patchset_num[index]
                cmd = _GERRIT_REVIEW_CMD_TEMPLATE % (port,
                                                     account,
                                                     server,
                                                     "\\\'" + msg + "\\\'",
                                                     change_num,
                                                     patch_set) + " --verified -1"
                TIMEOUT_SHELL_COMMAND(cmd, timeout=60)
        exit(1)


def TIMEOUT_SHELL_COMMAND(command:str, timeout=60, capture_output=False, encoding ='utf8', cwd=None) -> list:
    # cmd = command
    # start = datetime.datetime.now()
    # if mode == 'pipe':
        # process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding=encode_mode, cwd=working_path)

    #   while process.poll() is None:
    #     time.sleep(0.2)
    #     now = datetime.datetime.now()
    #     if (now - start).seconds > timeout:
    #       os.kill(process.pid, signal.SIGKILL)
    #       os.waitpid(-1, os.WNOHANG)
    #       LOG(LEVEL_INFO, 'Process CMD TIMEOUT: [{}] [{}] [{}] [{}]'.format(command, mode, timeout, working_path))
    #       return 2
    #   LOG(LEVEL_INFO, 'Process CMD SUCCESSFUL: [{}] [{}] [{}] [{}]'.format(command, mode, timeout, working_path))

    #   return process.stdout.readlines()
    # else:
    #   process = subprocess.run(cmd, shell=True)
    cmd = command
    LOG(LEVEL_INFO, 'Process CMD START: [cmd={}] [timeout={}] [capture_out={}] [encoding={}] [cwd={}]'.format(
        cmd, timeout, capture_output, encoding, cwd))

    process = subprocess.run(cmd,
                             shell=True,
                             timeout=timeout,
                             capture_output=capture_output,
                             encoding=encoding,
                             cwd=cwd)
    LOG(LEVEL_INFO, 'Process CMD END: [returncode={}] [cmd={}] [timeout={}] [capture_out={}] [encoding={}] [cwd={}]'.format(
        process.returncode ,cmd, timeout, capture_output, encoding, cwd))
    if process.stdout:
        # LOG(LEVEL_INFO, 'CMD stdout = [{}]'.format(process.stdout))
        # if process.stderr:
        #   LOG(LEVEL_INFO, 'CMD stderr = [{}]'.format(process.stderr))
        if (not process.returncode):
            return [process.returncode, process.stdout.strip().split('\n')]
    else:
        return [process.returncode, []]


class _GuardInfo(object):
    def __init__(self) -> None:
        self.account_id = ''
        self.change_id = ''
        self.output_type = ''
        self.org_gerrit_data = []
        self._gerrit_project = []
        self._gerrit_project_count = 0
        self._gerrit_branch = ''
        self._gerrit_number = []
        self._gerrit_url = []
        self._gerrit_patchset_num = []
        self._gerrit_ref_info = []
        self._project_name = ''
        self._gerrit_file_list = []
        self._working_dir = ''
        self._code_root_path = ''
        self._manifest_project_path_dict = []
        self._cherrypick_cmd_list = []
        self._copied_image_path = {
            'earbud': {'root': 'earbud_img', 'org': 'earbud_img/org/', 'patch': 'earbud_img/patch/'},
            'box' : {'root': 'box_img', 'org': 'box_img/org/', 'patch': 'box_img/patch'},
        }
        self.jenkins_url = ''
        self.tws_lint_success = False
        self.cppcheck_success = False
        self.impact_build_success = False

    def _SetGerritProjectCount(self, count):
        self._gerrit_project_count = count

    def GetGerritProjectCount(self):
        return self._gerrit_project_count

    def _SetGerritProjectInfo(self, proj):
        """
        'project': 'HQ_BD1SW_PA1952/earphone'
        """
        self._gerrit_project.append(proj)
        self._SetGerritProjectCount(len(self._gerrit_project))

    def GetGerritProjectInfo(self, index):
        """
        example: 'project': 'HQ_BD1SW_PA1952/earphone'
        """
        if index >= len(self._gerrit_project):
            LOG(LEVEL_ERROR, 'The count of the Gerrit Project is [{}], but you set index [{}]'.format(
                len(self._gerrit_project), index))
        return self._gerrit_project[index]

    def _SetGerritBranchInfo(self, info):
        """
        example: 'branch': 'tws_master_202208301600'
        """
        if (info + ".xml") != self.get_manifest_name():
            LOG(LEVEL_WARN, f"The branch you set [{info}], is not same with the project you set [{self.get_manifest_name()}].")
            return None
        self._gerrit_branch = info
        return self._gerrit_branch

    def GetGerritBranchInfo(self):
        """
        example: 'branch': 'tws_master_202208301600'
        """
        return self._gerrit_branch

    def _SetGerritNumber(self, number:str):
        """
        example: 'number': '256832'
        """
        if len(number) != 6:
            LOG(LEVEL_ERROR, 'Gerrit number should have 6 digits, but you set [{}]'.format(number))
        self._gerrit_number.append(number)

    def GetGerritNumber(self, index) -> str:
        """
        example: 'number': '256832'
        """
        if index >= len(self._gerrit_number):
            LOG(LEVEL_ERROR, 'The count of the Gerrit Number is [{}], but you set index [{}]'.format(
                len(self._gerrit_number), index))
        return self._gerrit_number[index]

    def _SetGerritUrl(self, url:str):
        """
        example: 'url': 'https://gerrit.huaqin.com:9443/256832'
        """
        self._gerrit_url.append(url)

    def GetGerritUrl(self, index) -> str:
        """
        example: 'url': 'https://gerrit.huaqin.com:9443/256832'
        """
        if index >= len(self._gerrit_url):
            LOG(LEVEL_ERROR, 'The count of the Gerrit URL is [{}], but you set index [{}]'.format(
                len(self._gerrit_url), index))
        self._gerrit_url[index]

    def _SetGerritPatchsetNum(self, number:str):
        """
        'currentPatchSet': {'number': '3'
        """
        self._gerrit_patchset_num.append(number)

    def GetGerritPatchSetNum(self, index:int) -> str:
        """
        'currentPatchSet': {'number': '3'
        """
        if index >= len(self._gerrit_patchset_num):
            LOG(LEVEL_ERROR, 'The count of the Patchset Number is [{}], but you set index [{}]'.format(
                len(self._gerrit_patchset_num), index))
        return self._gerrit_patchset_num[index]

    def _SetGerritRefInfo(self, ref):
        """
        'currentPatchSet': {ref': 'refs/changes/32/256832/3'
        """
        self._gerrit_ref_info.append(ref)

    def GetGerritRefInfo(self, index):
        """
        'currentPatchSet': {'ref': 'refs/changes/32/256832/3'
        """
        if index >= len(self._gerrit_ref_info):
            LOG(LEVEL_ERROR, 'The count of the Ref info is [{}], but you set index [{}]'.format(
                len(self._gerrit_ref_info), index))
        return self._gerrit_ref_info[index]

    def SetWorkingPath(self, path):
        if len(path) < len(_WORKING_ROOT_PATH):
            LOG(LEVEL_ERROR, 'Working path should under the [{}], but you set [{}]'.format(
                _WORKING_ROOT_PATH, path))
        if path[:len(_WORKING_ROOT_PATH)] != _WORKING_ROOT_PATH:
            LOG(LEVEL_ERROR, 'Working path you set do not support, please check. Path should start with [{}], but you set [{}]'.format(
                _WORKING_ROOT_PATH, path))
        self._working_dir = path

    def GetWorkingPath(self) -> str:
        if len(self._working_dir) < len(_WORKING_ROOT_PATH):
            LOG(LEVEL_ERROR, 'Working path not set yet, please set it first: [{}]'.format(self._working_dir))
        if self._working_dir[:len(_WORKING_ROOT_PATH)] != _WORKING_ROOT_PATH:
            LOG(LEVEL_ERROR, 'Working path is wrong, please check, Path should start with [{}], but now is [{}]'.format(
                _WORKING_ROOT_PATH, self._working_dir))
        return self._working_dir

    def SetCodePath(self, path):
        if len(path) <= len(self.GetWorkingPath()):
            LOG(LEVEL_ERROR, 'Code path should under the [{}], but you set [{}]'.format(
                self.GetWorkingPath(), path))
        if path[:len(self.GetWorkingPath())] != self.GetWorkingPath():
            LOG(LEVEL_ERROR, 'Code path you set do not support, please check. Path should start with [{}], but you set [{}]'.format(
                self.GetWorkingPath(), path))
        self._code_root_path = path

    def GetCodeRootPath(self) -> str:
        if len(self._code_root_path) <= len(self.GetWorkingPath()):
            LOG(LEVEL_ERROR, 'Code path not set yet, please set it first: [{}]'.format(self._code_root_path))
        if self._code_root_path[:len(self.GetWorkingPath())] != self.GetWorkingPath():
            LOG(LEVEL_ERROR, 'Code path is wrong, please check. Path should start with [{}], but now is [{}]'.format(
                self.GetWorkingPath(), self._code_root_path))
        return self._code_root_path

    def _SetGerritFileInfo(self, files):
        """
        [
            {'file': '/COMMIT_MSG', 'type': 'ADDED', 'insertions': 18, 'deletions': 0},
            {'file': 'app/ux/src/ux_box.c', 'type': 'MODIFIED', 'insertions': 8, 'deletions': 0},
            {'file': 'app/ux/src/ux_sensor_interceptor.c', 'type': 'MODIFIED', 'insertions': 1, 'deletions': -1},
            {'file': 'include/platform/frameworks/utils/kernel_property.h', 'type': 'MODIFIED', 'insertions': 3, 'deletions': 0},
            {'file': 'platform/frameworks/property/include/kv_adapter.h', 'type': 'MODIFIED', 'insertions': 3, 'deletions': 0},
            {'file': 'platform/frameworks/property/property.c', 'type': 'MODIFIED', 'insertions': 7, 'deletions': -1},
            {'file': 'platform/frameworks/property/src/bes/kv_adapter.c', 'type': 'MODIFIED', 'insertions': 14, 'deletions': 0},
            {'file': 'platform/frameworks/property/src/wq/kv_adapter.c', 'type': 'MODIFIED', 'insertions': 11, 'deletions': -1}
        ]
        """
        self._gerrit_file_list.append(files)

    def GetGerritFileInfo(self, index):
        if index >= len(self._gerrit_file_list):
            LOG(LEVEL_ERROR, 'The count of the file list Number is [{}], but you set index [{}]'.format(
                len(self._gerrit_file_list), index))
        return self._gerrit_file_list[index]

    def _SetProjectName(self, name):
        """
        ts6239
        """
        if not _PROJECT_CONFIG_INFO.get(name.upper()):
            LOG(LEVEL_ERROR, 'We can not find the project for [{}]'.format(name))
        self._project_name = name.upper()

    def GetProjectName(self):
        if not self._project_name:
            LOG(LEVEL_ERROR, 'Project Name not set yet, please set it first')
        return self._project_name

    def _CheckGerritOpenStatus(self, status):
        """
        'open': True
        """
        if status != True:
            LOG(LEVEL_ERROR, 'The gerrit status is not OPEN, please check')

    def GetCodePullCmd(self):
        cmd = _CODE_PULL_CMD_TEMPLATE % (self.account_id,
                                        _PROJECT_CONFIG_INFO.get(self._project_name).get('code_server'),
                                        _PROJECT_CONFIG_INFO.get(self._project_name).get('code_server_port'),
                                        _PROJECT_CONFIG_INFO.get(self._project_name).get('manifest')
                                        )
        return cmd

    def GetEarphoneBuildCmd(self) -> str:
        cmd = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_cmd')
        if not cmd:
            LOG(LEVEL_ERROR, 'Build CMD get fail, please check the _PROJECT_CONFIG_INFO')
            return ''

        return cmd

    def GetEarphoneBuildPath(self) -> str:
        if not self.GetCodeRootPath():
            LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
        path = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_dir')
        if not path:
            LOG(LEVEL_ERROR, 'Cannot get earphone build path, please check the earphone_build_dir in _PROJECT_CONFIG_INFO')
            return ''

        # test_file = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_test_file')
        # if not test_file:
        #   LOG(LEVEL_ERROR, 'Cannot get earphone build test file, Please check the earphone_build_test_file in _PROJECT_CONFIG_INFO')
        # test_file_path = self.GetCodeRootPath() +  test_file
        # if not os.path.isfile(test_file_path):
        #   LOG(LEVEL_ERROR, 'Cannot get earphone build test file, please check if the file exist')
        return self.GetCodeRootPath() + path

    def GetEarphoneBuildOutputPath(self) -> str:
        if not self.GetCodeRootPath():
            LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
        path = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_path')
        if not path:
            LOG(LEVEL_ERROR, 'Cannot get earphone build path, please check the earphone_build_output_path in _PROJECT_CONFIG_INFO')
            return ''
        return self.GetCodeRootPath() + path

    def GetEarphoneBuildTestFilePath(self) -> str:
        if not self.GetCodeRootPath():
            LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
        test_file = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_test_file')
        if not test_file:
            LOG(LEVEL_ERROR, 'Cannot get earphone build test file, Please check the earphone_build_test_file in _PROJECT_CONFIG_INFO')
        return self.GetCodeRootPath() +  test_file

    def GetEarphoneCopyFileList(self) -> list:
        work_path = self.GetCodeRootPath()
        path  = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_file_path')
        filelist = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_file_list')
        ret = []
        if (not path) or (not filelist):
            LOG(LEVEL_ERROR, 'Get earphone copy file list fail. [{}] [{}]'.format(path, filelist))
        for file in filelist:
            ret.append(work_path + path + file)

        LOG(LEVEL_INFO, filelist)
        return ret

    def GetCherrypickPathByGerryProjectName(self, name) -> str:
        if not self._manifest_project_path_dict:
            LOG(LEVEL_ERROR, "Manifest xml not parse yet.")
        for node in self._manifest_project_path_dict:
            if node.get('name') == name:
                return self.GetCodeRootPath() + node.get('path') + '/'
        LOG(LEVEL_ERROR, 'Cannot find the project [{}] in [{}] configuration'.format(
            name, self.GetProjectName()))
        return ''

    def GetCherrypickCommand(self, index) -> str:
        if not self._cherrypick_cmd_list:
            LOG(LEVEL_ERROR, 'Cherrypick command not set yet.')
        if index >= len(self._cherrypick_cmd_list):
            LOG(LEVEL_ERROR, 'Cherrypick command index error, we have [{}], but you want [{}]'.format(
                len(self._cherrypick_cmd_list), index + 1))
        return self._cherrypick_cmd_list[index]

    def GetImagePath(self, product, build_type) -> str:
        if not self._copied_image_path:
            LOG(LEVEL_ERROR, 'image path not set yet, please check')
        if (product == 'earbud') or (product == 'box'):
            if (build_type == 'org') or (build_type == 'patch'):
                return self.GetWorkingPath() + self._copied_image_path.get(product).get(build_type)
        LOG(LEVEL_ERROR, 'product [{}] or build_type [{}] error, please check'.format(product, build_type))
        return ''

    def GetCopiedImageRootPath(self, product) -> str:
        if not self._copied_image_path:
            LOG(LEVEL_ERROR, 'image path not set yet, please check')
        if (product == 'earbud') or (product == 'box'):
            return self.GetWorkingPath() + self._copied_image_path.get(product).get('root')
        LOG(LEVEL_ERROR, 'product [{}] error, please check'.format(product))
        return ''

    def GetImageFilePath(self, product, build_type, img_type) -> str:
        if self.GetEarbudPlatformType() == 'wq':
            _supported_img_type = ['map', 'bin', 'layout', 'pkg']
        else:
            _supported_img_type = ['map', 'bin']

        if not self.GetImagePath(product, build_type):
            LOG(LEVEL_ERROR, 'Get image path fail when get file path, please check')
        if img_type not in _supported_img_type:
            LOG(LEVEL_ERROR, 'Get image file path fail, please check img_type you set')
        return self.GetImagePath(product, build_type) + _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('img_file_dict').get(product).get(img_type)

    def GetJenkinsUrl(self) -> str:
        return self.jenkins_url

    def GetEarbudPlatformType(self) -> str:
        return _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('platform_type')

    def GetImpactProjectBuildInfo(self) -> list:
        impact_project_list = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get("impact_project_list")
        impact_build_info = []
        if not impact_project_list:
            return impact_project_list

        for project_name in impact_project_list:
            if project_name == self.GetProjectName():
                continue
            project_config = _PROJECT_CONFIG_INFO.get(project_name)
            build_path = self.GetCodeRootPath() + project_config.get("earphone_build_dir")
            build_cmd = project_config.get("earphone_build_cmd")
            output_path = self.GetCodeRootPath() + project_config.get("earphone_build_output_path")
            test_file = self.GetCodeRootPath() + project_config.get("earphone_build_test_file")
            output_file_path = self.GetCodeRootPath() + project_config.get("earphone_build_output_file_path")
            copy_file_list = []
            for file in project_config.get("earphone_build_output_file_list"):
                copy_file_list.append(output_file_path + file)
            impact_build_info.append((project_name, build_path, build_cmd, output_path, test_file, copy_file_list))

        return impact_build_info

    def NotifyReviewToGerrit(self, message:str, review=None):
        port = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('review_server_port')
        account = self.account_id
        server = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('review_server')
        msg = '[TWS_GUARD] ' + message
        for index in range(0, self.GetGerritProjectCount()):
            change_num = self.GetGerritNumber(index)
            patch_set = self.GetGerritPatchSetNum(index)
            cmd = _GERRIT_REVIEW_CMD_TEMPLATE % (port, account, server, "\\\'" + msg + "\\\'", change_num, patch_set)
            if review:
                cmd += review
            ret = TIMEOUT_SHELL_COMMAND(cmd, timeout=60)
            if ret[0]:
                LOG(LEVEL_WARN, 'Gerrit Review Fail, cmd= [{}]'.format(cmd))

    def ParseGerritInfo(self):
        if not self.org_gerrit_data:
            LOG(LEVEL_ERROR, "Please set 'org_gerrit_data' first")
            return
        if len(self.org_gerrit_data) <= 1:
            LOG(LEVEL_ERROR, 'No gerrit info found.')

        for line_num in range(0, len(self.org_gerrit_data) - 1):
            json_data = json.loads(self.org_gerrit_data[line_num])
            if json_data:
                if json_data.get("status") in ("ABANDONED", "MERGED"):
                    LOG(LEVEL_WARN, f"Parsing gerrit index [{line_num}] error, gerrit status error, status: [{json_data.get('status')}], branch: [{json_data.get('branch')}]")
                    continue
                if json_data.get('branch'):
                    ret = self._SetGerritBranchInfo(json_data.get('branch'))
                    if not ret:
                        LOG(LEVEL_WARN, f"Parsing gerrit index [{line_num}] error, branch name missmatch. [{json_data.get('branch')}], [{self.get_manifest_name()}]")
                        continue
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('branch'))

                if json_data.get('project'):
                    self._SetGerritProjectInfo(json_data.get('project'))
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('project'))

                if json_data.get('number'):
                    self._SetGerritNumber(json_data.get('number'))
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('number'))

                if json_data.get('url'):
                    self._SetGerritUrl(json_data.get('url'))
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('url'))

                if json_data.get('currentPatchSet'):
                    if json_data.get('currentPatchSet').get('number'):
                        self._SetGerritPatchsetNum(json_data.get('currentPatchSet').get('number'))
                    else:
                        LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet.number'))

                    if json_data.get('currentPatchSet').get('ref'):
                        self._SetGerritRefInfo(json_data.get('currentPatchSet').get('ref'))
                    else:
                        LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('ref'))

                    if json_data.get('currentPatchSet').get('files'):
                        self._SetGerritFileInfo(json_data.get('currentPatchSet').get('files'))
                    else:
                        LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet.files'))
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet'))

                if json_data.get('open'):
                    self._CheckGerritOpenStatus(json_data.get('open'))
                else:
                    LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('open status'))

        if not self.GetGerritBranchInfo():
            LOG(LEVEL_ERROR, "Parsing gerrit info error, No valid gerrit commit found with given change ID, please check the changeId's status.")
        # self.SetWorkingPath(_WORKING_ROOT_PATH + self.change_id[-8:] + '/')
        self.SetWorkingPath(_WORKING_ROOT_PATH)
        self.SetCodePath(self.GetWorkingPath() + 'code/')

        LOG(LEVEL_INFO, 'Start parsing cherrypick command')
        cherrypick_server = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('cherrypick_server')
        cherrypick_server_port = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('cherrypick_server_port')
        for index in range(0, self.GetGerritProjectCount()):
            cmd = _CHERRYPICK_CMD_TEMPLATE % (
                                            self.account_id,
                                            cherrypick_server,
                                            cherrypick_server_port,
                                            self.GetGerritProjectInfo(index),
                                            self.GetGerritRefInfo(index))
            self._cherrypick_cmd_list.append(cmd)
        LOG(LEVEL_INFO, 'Parsing cherrypick command finished')

    def ParseProjectManifestInfo(self):
        LOG(LEVEL_INFO, 'Start parsing manifest: [{}]'.format(self.GetProjectName()))
        manifest = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('manifest')
        if not manifest:
            LOG(LEVEL_ERROR, 'Cannot fild manifest configuration in _PROJECT_CONFIG_INFO, please check')
        manifest_path = self.GetCodeRootPath() + '.repo/manifests/' + manifest
        xml_data = XMLET.parse(manifest_path)
        root = xml_data.getroot()
        for child in root:
            if child.tag == 'project':
                self._manifest_project_path_dict.append(child.attrib)
        LOG(LEVEL_INFO, 'Parsing manifest Finished: [{}]'.format(self.GetProjectName()))

    def get_manifest_name(self):
        """_summary_

        Returns:
            _type_: _description_
        """
        manifest = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('manifest')
        if manifest:
            return os.path.basename(manifest)
        else:
            return None


_guardInfo = _GuardInfo()


def _QueryGerritInfo():
    query_command = "ssh -p 29418 %s@gerrit.huaqin.com gerrit query %s --current-patch-set --format JSON --files" % (
        _guardInfo.account_id, _guardInfo.change_id)

    ret = TIMEOUT_SHELL_COMMAND(query_command, capture_output=True, timeout=30)
    if ret[0]:
        LOG(LEVEL_ERROR, 'Query gerrit info fail, please check the return code: [{}]'.format(ret[0]))
    # for line in gerrit_lines:
    #   LOG(LEVEL_INFO, json.loads(line))
    _guardInfo.org_gerrit_data = ret[1]
    _guardInfo.ParseGerritInfo()


def _InitReviewerWorkingEnv():
    LOG(LEVEL_INFO, 'Init the working path: [{}]'.format(_guardInfo.GetCodeRootPath()))

    if os.path.exists(_guardInfo.GetWorkingPath()):
        LOG(LEVEL_INFO, 'Removing the already existing dir: [{}]'.format(_guardInfo.GetWorkingPath()))
        ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (_guardInfo.GetWorkingPath()), timeout=90)
        if ret[0]:
            LOG(LEVEL_ERROR, 'Fail to delete the exist working path: [{}]'.format(_guardInfo.GetWorkingPath()))

    os.makedirs(_guardInfo.GetWorkingPath())
    if (not os.path.exists(_guardInfo.GetWorkingPath())) or (not os.access(_guardInfo.GetWorkingPath(), mode=os.W_OK)):
        LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(_guardInfo.GetWorkingPath()))

    os.mkdir(_guardInfo.GetCodeRootPath())
    if (not os.path.exists(_guardInfo.GetCodeRootPath())) or (not os.access(_guardInfo.GetCodeRootPath(), mode=os.W_OK)):
        LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(_guardInfo.GetCodeRootPath()))

    LOG(LEVEL_INFO, 'Init the working environment successful: [{}]'.format(_guardInfo.GetWorkingPath()))


def _PullProjectCode():
    LOG(LEVEL_INFO, 'Starting to pull the code to dir [{}]'.format(_guardInfo.GetWorkingPath()))
    pull_command = _guardInfo.GetCodePullCmd()

    ret = TIMEOUT_SHELL_COMMAND(command=pull_command, timeout=900, cwd=_guardInfo.GetCodeRootPath())
    if ret[0]:
        LOG(LEVEL_ERROR, 'Pull code fail, please check. cmd:[{}]'.format(pull_command))
    test_dir = _guardInfo.GetCodeRootPath() + 'earphone/'
    if (not os.path.exists(test_dir)) or (not os.access(test_dir, mode=os.W_OK)):
        LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(test_dir))

    LOG(LEVEL_INFO, 'Pull the code successful [{}]'.format(_guardInfo.GetCodeRootPath()))


def _build_impact_project_earphone():
    build_info = _guardInfo.GetImpactProjectBuildInfo()
    gerrit_msg = ""
    impact_build_success = True
    if build_info:
        for project_name, build_path, build_cmd, output_path, test_file, copy_file_list in build_info:
            if not os.path.isfile(test_file):
                impact_build_success = False
                gerrit_msg += "FAIL project: [{}], info: [{}]\\\n\\\r".format(
                    project_name, "testfile [{}] not exist.".format(test_file)
                )
                break
            if os.path.exists(output_path):
                ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (output_path), timeout=90)
                if ret[0]:
                    impact_build_success = False
                    gerrit_msg += "FAIL project: [{}], info: [{}]\\\n\\\r".format(
                        project_name, "cannot delete output path [{}].".format(output_path)
                    )
                    break
            if not os.access(build_path, mode=os.O_RDWR):
                impact_build_success = False
                gerrit_msg += "FAIL project: [{}], info: [{}]\\\n\\\r".format(
                    project_name, "cannot access build path [{}].".format(build_path)
                )
                break

            ret = TIMEOUT_SHELL_COMMAND(build_cmd, timeout=900, cwd=build_path)
            if ret[0]:
                impact_build_success = False
                gerrit_msg += "FAIL project: [{}], info: [{}]\\\n\\\r".format(
                    project_name, "build project fail [{}]."
                )
                break

            for file in copy_file_list:
                if not os.path.isfile(file):
                    impact_build_success = False
                    gerrit_msg += "FAIL project: [{}], info: [{}]\\\n\\\r".format(
                        project_name, "Build output file does not exist [{file}]."
                    )
                    break
            if not impact_build_success:
                break

            gerrit_msg += "PASS project: [{}]\\\n\\\r".format(project_name)

        if impact_build_success == True:
            _guardInfo.impact_build_success = True
            gerrit_msg = "[Build Impact project] Successful. \\\n\\\r" + gerrit_msg
        else:
            _guardInfo.impact_build_success = False
            gerrit_msg = "[Build Impact project] Failed. \\\n\\\r" + gerrit_msg
        _guardInfo.NotifyReviewToGerrit(gerrit_msg)
    else:
        _guardInfo.impact_build_success = True
        gerrit_msg = "[Build Impact project] Successful: No Porject need to check\\\n\\\r"
        _guardInfo.NotifyReviewToGerrit(gerrit_msg)


def _BuildEarphone(copy_dst_path):
    LOG(LEVEL_INFO, 'Start to build the Earphone')
    build_path = _guardInfo.GetEarphoneBuildPath()
    build_cmd = _guardInfo.GetEarphoneBuildCmd()
    output_path = _guardInfo.GetEarphoneBuildOutputPath()
    test_file = _guardInfo.GetEarphoneBuildTestFilePath()

    if not os.path.isfile(test_file):
        LOG(LEVEL_ERROR, 'Cannot find the earphone test file [{}], please check.'.format(test_file))
    if os.path.exists(output_path):
        ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (output_path), timeout=90)
        if ret[0]:
            LOG(LEVEL_ERROR, 'Cannot delete existing output dir: [ret={}] [{}]'.format(ret, output_path))
    if not os.access(build_path, mode=os.O_RDWR):
        LOG(LEVEL_ERROR, 'Cannot access build path [{}]'.format(build_path))

    ret = TIMEOUT_SHELL_COMMAND(build_cmd, timeout=900, cwd=build_path)
    if ret[0]:
        LOG(LEVEL_ERROR, 'Earphone build command execute Fail, ret = [{}]'.format(ret[0]))
    LOG(LEVEL_INFO, 'Earphone build successful.')

    if copy_dst_path:
        LOG(LEVEL_INFO, 'Start copy output file to [{}]'.format(copy_dst_path))
        if os.path.exists(copy_dst_path):
            ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (copy_dst_path), timeout=30)
            if ret[0]:
                LOG(LEVEL_ERROR, 'Cannot delete the existing copy destination dir: [{}]'.format(copy_dst_path))
        os.makedirs(copy_dst_path)
        if not os.path.exists(copy_dst_path):
            LOG(LEVEL_ERROR, 'Create copy destination dir fail: [{}]'.format(copy_dst_path))

        for file in _guardInfo.GetEarphoneCopyFileList():
            ret = TIMEOUT_SHELL_COMMAND('cp %s %s' % (file, copy_dst_path), timeout=10)
            if ret[0]:
                LOG(LEVEL_ERROR, 'Earphone copy file fail: from=[{}] to=[{}]'.format(file, copy_dst_path))
        LOG(LEVEL_INFO, 'Copy output files successful [{}]'.format(copy_dst_path))

        build_type = copy_dst_path.split('/')[-1]
        if copy_dst_path[-1] == '/':
            build_type = copy_dst_path.split('/')[-2]
        LOG(LEVEL_INFO, 'copy_dst_path = [{}], build_type = [{}]'.format(copy_dst_path, build_type))
        if build_type == 'patch':
            LOG(LEVEL_INFO, 'Zip the {} image files Start'.format(build_type))
            zip_name = '%s_image.zip' % (build_type)
            cmd = 'zip -r %s  %s' % (zip_name, copy_dst_path)
            exe_cwd = _guardInfo.GetCopiedImageRootPath('earbud')
            ret = TIMEOUT_SHELL_COMMAND(cmd, timeout=90, cwd=exe_cwd)
            if ret[0]:
                LOG(LEVEL_INFO, 'Zip the {} image files fail'.format(build_type))
            else:
                LOG(LEVEL_INFO, 'Zip the {} image files successful'.format(build_type))
                gerrit_msg = '[Patch Build] SUCCESSFUL  \\\n\\\r'
                # http://192.168.29.22:8080/job/tws_guard_2/24/artifact/working/cppcheck_report.zip
                result_url = "%sartifact/%s%s" % (_guardInfo.jenkins_url, _WORKING_ROOT_DIR, zip_name)
                gerrit_msg += 'You can download the patch build image in {}'.format(result_url)
                _guardInfo.NotifyReviewToGerrit(gerrit_msg)


def _CherrypickThePatch():
    'git fetch ssh://101002563@gerrit.huaqin.com:29418/HQ_BD1SW_PA1952/earphone refs/changes/78/256878/1 && git cherry-pick FETCH_HEAD'
    for index in range( _guardInfo.GetGerritProjectCount()):
        cherrypick_cmd = _guardInfo.GetCherrypickCommand(index)
        cherrypick_path = _guardInfo.GetCherrypickPathByGerryProjectName(_guardInfo.GetGerritProjectInfo(index))
        ret = TIMEOUT_SHELL_COMMAND(cherrypick_cmd, timeout=180, cwd=cherrypick_path)
        if ret[0]:
            gerrit_msg = '[Patch Build] Cherry-pick Fail'
            _guardInfo.NotifyReviewToGerrit(gerrit_msg)
            LOG(LEVEL_ERROR, 'Cherry-pick fail: [{}] [{}]'.format(cherrypick_cmd, cherrypick_path))


def _DoBinSizeCheck():
    org_bin_path = _guardInfo.GetImageFilePath('earbud', 'org', 'bin')
    patch_bin_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'bin')
    org_bin_size = os.path.getsize(org_bin_path)
    patch_bin_size = os.path.getsize(patch_bin_path)
    diff_size = patch_bin_size - org_bin_size

    LOG(LEVEL_INFO, 'patch_bin_size: [{}] - org_bin_size [{}] = [{}] Bytes.'.format(
            patch_bin_size, org_bin_size, diff_size))
    review_info = '[BIN Size Check]\\\n\\\r'
    review_info = review_info + 'patch_bin_size: [{}] - org_bin_size [{}] = [{}] Bytes.'.format(
            patch_bin_size, org_bin_size, diff_size)
    _guardInfo.NotifyReviewToGerrit(review_info)
    pass


def _DoBesFreeRamCheck():
    _VALID_FREERAM_LIST = ['__free_ram', '__free_ramcp', '__free_ramcpx', '__free_fram']
    def _FindFreeRamLines(path) -> dict:
        _ALL_FREERAM_PATTERN = re.compile(r'__free_ram|__free_fram')
        ret_dict = {}
        fd = open(path, mode='r', encoding='utf8')
        lines = fd.readlines()
        fd.close()
        for line in lines:
            if _ALL_FREERAM_PATTERN.search(line):
                line = line.strip()
                info = re.split(r' +', line, 2)
                if info[1] not in _VALID_FREERAM_LIST:
                    continue
                if info[0][:2] != '0x':
                    continue
                ret_dict.setdefault(info[1], info[0])
        if len(ret_dict) != 4:
            LOG(LEVEL_ERROR, 'BES free ram check fail, the lines we found are [{}]'.format(ret_dict))
        return ret_dict

    org_map_path = _guardInfo.GetImageFilePath('earbud', 'org', 'map')
    patch_map_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'map')
    org_ram_dict = _FindFreeRamLines(org_map_path)
    patch_ram_dict = _FindFreeRamLines(patch_map_path)
    LOG(LEVEL_INFO, 'ORG  : [{}]'.format(org_ram_dict))
    LOG(LEVEL_INFO, 'PATCH: [{}]'.format(patch_ram_dict))

    review_info = '[BES free_ram check]\\\n\\\r'
    for item in _VALID_FREERAM_LIST:
        org_hex_str = org_ram_dict.get(item)
        org_dec = int(org_hex_str, base=16)
        patch_hex_str = patch_ram_dict.get(item)
        patch_dec = int(patch_hex_str, base=16)
        diff_dec = patch_dec - org_dec
        LOG(LEVEL_INFO, '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]'.format(
                item, patch_hex_str, patch_dec, org_hex_str, org_dec, diff_dec))
        review_info = review_info + '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]\\\n\\\r'.format(
                item, patch_hex_str, patch_dec, org_hex_str, org_dec, diff_dec)

    _guardInfo.NotifyReviewToGerrit(review_info)


def _DoWuqiMemoryConfigCheck():
    org_memcfg_path = _guardInfo.GetImageFilePath('earbud', 'org', 'layout')
    patch_memcfg_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'layout')
    fd = open(org_memcfg_path, 'r', encoding='utf8')
    org_fdata = fd.read()
    fd.close()

    fd = open(patch_memcfg_path, 'r', encoding='utf8')
    patch_fdata = fd.read()
    fd.close()

    review_info = '[WuQi memory config check]\\\n\\\r'

    org_json_data = json.loads(org_fdata)
    org_images_list = org_json_data.get("images")

    patch_json_data = json.loads(patch_fdata)
    patch_images_list = patch_json_data.get("images")
    for patch_image in patch_images_list:
        patch_image_name = patch_image.get('name')
        patch_image_length = patch_image.get('length')
        for org_image in org_images_list:
            org_image_name = org_image.get('name')
            org_image_length = org_image.get('length')
            if patch_image_name == org_image_name:
                # LOG(LEVEL_INFO, '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]'.format(
                #         item, patch_hex_str, patch_dec, org_hex_str, org_dec, diff_dec))
                # review_info = review_info + '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]\\\n\\\r'.format(
                #         item, patch_hex_str, patch_dec, org_hex_str, org_dec, diff_dec)
                break

    _guardInfo.NotifyReviewToGerrit(review_info)


def _DoWuqiBinSizeCheck():
    org_pkg_path = _guardInfo.GetImageFilePath("earbud", "org", "pkg")
    patch_pkg_path = _guardInfo.GetImageFilePath("earbud", "patch", "pkg")
    image_list = ("dsp_tws_app.bin", "fw_updater_core0.bin", "pbl_core0.bin",
                  "sbl_core0.bin", "tone.bin", "tws_core0.bin",
                  "tws_core1.bin")
    org_image_info_dict = {}
    patch_image_info_dict = {}
    with zipfile.ZipFile(org_pkg_path, "r") as pkg:
        for file_name in image_list:
            org_image_info_dict[file_name] = pkg.getinfo(file_name).file_size

    with zipfile.ZipFile(patch_pkg_path, "r") as pkg:
        for file_name in image_list:
            patch_image_info_dict[file_name] = pkg.getinfo(file_name).file_size

    review_info = '[Wuqi Bin size check]\\\n\\\r'
    patch_total_size = 0
    org_total_size = 0
    diff_total_size = 0
    for file_name in image_list:
        patch_bin_size = patch_image_info_dict.get(file_name)
        patch_total_size += patch_bin_size
        org_bin_size = org_image_info_dict.get(file_name)
        org_total_size += org_bin_size
        diff_size = patch_bin_size - org_bin_size
        diff_total_size += diff_size
        review_info = review_info + '[{:<20}]: patch: [{:<8}] - org [{:<8}] = [{:<5}] Bytes.\\\n\\\r'.format(
                file_name, patch_bin_size, org_bin_size, diff_size)

    review_info = review_info + '[{:<20}]: patch: [{:<8}] - org [{:<8}] = [{:<5}] Bytes.\\\n\\\r'.format(
            "TOTAL", patch_total_size, org_total_size, diff_total_size)
    _guardInfo.NotifyReviewToGerrit(review_info)


def _DoWuqiFreeRamCheck():
    org_map_path = _guardInfo.GetImageFilePath('earbud', 'org', 'map')
    patch_map_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'map')
    org_map_parser = map_analyzer.MapParser(org_map_path, "org", log_printer=LOG)
    org_map_parser.StartParse()
    patch_map_parser = map_analyzer.MapParser(patch_map_path, "org", log_printer=LOG)
    patch_map_parser.StartParse()
    org_ram_sections = {}
    patch_ram_sections = {}
    for org_section in org_map_parser.sections_list:
        if org_section.get(map_analyzer.SECTIONS_DICT_KEYS[2]) == "ram":
            # org_ram_sections['.iram_text'] = 0xa35c
            org_ram_sections[org_section.get(map_analyzer.SECTIONS_DICT_KEYS[3])] = org_section.get(map_analyzer.SECTIONS_DICT_KEYS[6])
    for patch_section in patch_map_parser.sections_list:
        if patch_section.get(map_analyzer.SECTIONS_DICT_KEYS[2]) == "ram":
            # org_ram_sections['.iram_text'] = 0xa35c
            patch_ram_sections[patch_section.get(map_analyzer.SECTIONS_DICT_KEYS[3])] = patch_section.get(map_analyzer.SECTIONS_DICT_KEYS[6])

    review_info = '[WuQi ram size check]\\\n\\\r'
    org_ram_size_total = 0
    patch_ram_size_total = 0
    for section_name in org_ram_sections:
        org_ram_size_hex = org_ram_sections.get(section_name)
        org_ram_size_dec = int(org_ram_size_hex, base=16)
        org_ram_size_total += org_ram_size_dec
        patch_ram_size_hex = patch_ram_sections.get(section_name)
        patch_ram_size_dec = int(patch_ram_size_hex, base=16)
        patch_ram_size_total += patch_ram_size_dec
        diff_dec = patch_ram_size_dec - org_ram_size_dec
        LOG(LEVEL_INFO, '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]'.format(
                section_name, patch_ram_size_hex, patch_ram_size_dec, org_ram_size_hex, org_ram_size_dec, diff_dec))
        review_info = review_info + '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]\\\n\\\r'.format(
                section_name, patch_ram_size_hex, patch_ram_size_dec, org_ram_size_hex, org_ram_size_dec, diff_dec)

    section_name = "Total"
    LOG(LEVEL_INFO, '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]'.format(
            section_name, hex(patch_ram_size_total), patch_ram_size_total, hex(org_ram_size_total), org_ram_size_total, patch_ram_size_total - org_ram_size_total))
    review_info = review_info + '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]\\\n\\\r'.format(
            section_name, hex(patch_ram_size_total), patch_ram_size_total, hex(org_ram_size_total), org_ram_size_total, patch_ram_size_total - org_ram_size_total)
    _guardInfo.NotifyReviewToGerrit(review_info)


def _DoResourceCheck():
    if _guardInfo.GetEarbudPlatformType() == 'bes':
        _DoBinSizeCheck()
        _DoBesFreeRamCheck()
        ## _DoBesResourceDetailCheck()
    elif _guardInfo.GetEarbudPlatformType() == 'wq':
        _DoWuqiBinSizeCheck()
        _DoWuqiFreeRamCheck()


def _DoTwsLintCheck():
    _TWS_LINT_FILTER_FILE_LIST = ("tgt_hardware.h", "res_audio_data.h", "res_audio_data_cn.h")
    _TWS_LINT_FILTER_PATH_LIST = ["product/earphone/ts6653a/chip",]
    lint_script = _TOOLS_PATH + 'tws_lint_v0.2.py'
    lint_configs = "_null_config_,"
    if _PROJECT_CONFIG_INFO.get(_guardInfo.GetProjectName()).get('check_safe_function'):
        lint_configs += "need_check_safe_func,"
    files = ''
    code_root_path = _guardInfo.GetCodeRootPath()
    code_root_path_length = len(code_root_path)
    tws_lint_result_filename = 'tws_lint_check_result.log'
    for index in range(0, _guardInfo.GetGerritProjectCount()):
        gerrit_project_name = _guardInfo.GetGerritProjectInfo(index)
        if gerrit_project_name in _PROJECT_CONFIG_INFO.get(_guardInfo.GetProjectName()).get('twslint_project'):
            code_path = _guardInfo.GetCherrypickPathByGerryProjectName(gerrit_project_name)
            for file in _guardInfo.GetGerritFileInfo(index):
                if file.get('file') == '/COMMIT_MSG':
                    continue
                if file.get("type") == "DELETED":
                    continue
                if file.get('file')[file.get('file').rfind('.') + 1:] not in ['h', 'c']:
                    continue
                if os.path.basename(file.get('file')) in _TWS_LINT_FILTER_FILE_LIST:
                    continue
                _file_path_in_filter = False
                for path in _TWS_LINT_FILTER_PATH_LIST:
                    if path in file.get('file'):
                        _file_path_in_filter = True
                        break
                if _file_path_in_filter:
                    continue
                files += code_path + file.get('file') + ' '
    lines = []
    if files:
        cmd = 'python3 %s %s %s' % (lint_script, lint_configs, files)
        ret = TIMEOUT_SHELL_COMMAND(cmd, timeout=120,capture_output=True)

        if not ret[0] and ret[1]:
            for line in ret[1]:
                if line[:code_root_path_length] == code_root_path:
                    lines.append(line[code_root_path_length:])
                else:
                    lines.append(line)

        lint_result_file_path = _guardInfo.GetWorkingPath() + tws_lint_result_filename
        if os.path.exists(lint_result_file_path):
            cmd = 'rm -rf %s' % (lint_result_file_path)
            TIMEOUT_SHELL_COMMAND(cmd)

        try:
            fd = open(lint_result_file_path, 'w', encoding='utf8')
            for line in lines:
                fd.write(line + '\n')
            fd.close()
        except Exception as e:
            LOG(LEVEL_WARN, 'Cannot open tws lint result file [{}], please check, [{}]'.format(lint_result_file_path, e))

    if not lines:
        review_comments = '[TWS_LINT] PASS  '
        _guardInfo.tws_lint_success = True
    else:
        review_comments = '[TWS_LINT] FAIL  \\\n\\\r'
        # http://192.168.29.22:8080/job/tws_guard_2/24/artifact/working/tws_lint_check_result.log
        review_comments += 'You can check the TWS_LINT log in {}artifact/{}{}'.format(
                _guardInfo.jenkins_url, _WORKING_ROOT_DIR, tws_lint_result_filename)
    _guardInfo.NotifyReviewToGerrit(review_comments)


def _DoCppcheck():
    cppcheck_script = _TOOLS_PATH + 'cppcheck/cppcheck'
    reporthtml_script = _TOOLS_PATH + 'cppcheck/htmlreport/cppcheck-htmlreport'
    reporthtml_report_dir = "cppcheck_report"
    reporthtml_report_path = _WORKING_ROOT_PATH + reporthtml_report_dir + "/"
    cppcheck_outputfile = _WORKING_ROOT_PATH + 'cppcheck_result.xml'
    cppcheck_file_filter_attr = '--file-filter='
    cppcheck_file_filter = ''
    code_root_path = _guardInfo.GetCodeRootPath()
    code_root_path_length = len(code_root_path)
    cppcheck_path = ''
    for index in range(0, _guardInfo.GetGerritProjectCount()):
        gerrit_project_name = _guardInfo.GetGerritProjectInfo(index)
        if gerrit_project_name in _PROJECT_CONFIG_INFO.get(_guardInfo.GetProjectName()).get('cppcheck_project'):
            code_path = _guardInfo.GetCherrypickPathByGerryProjectName(gerrit_project_name)
            cppcheck_path += code_path + ' '
            for file in _guardInfo.GetGerritFileInfo(index):
                if file.get('file') == '/COMMIT_MSG':
                    continue
                if file.get("type") == "DELETED":
                    continue
                if file.get('file')[file.get('file').rfind('.') + 1:] not in ['c', 'cpp']:
                    continue
                # files += code_path + file.get('file') + ' '
                cppcheck_file_filter += cppcheck_file_filter_attr + code_path + file.get('file') + ' '

    if cppcheck_file_filter:
        if os.path.exists(cppcheck_outputfile):
            cmd = 'rm %s' % (cppcheck_outputfile)
            TIMEOUT_SHELL_COMMAND(cmd)
        check_suppress = ""
        check_suppress += " " + "--suppress=unusedFunction"
        check_suppress += " " + "--suppress=missingInclude"
        check_suppress += " " + "--suppress=unmatchedSuppression"
        check_suppress += " " + "--suppress=unusedStructMember"
        check_suppress += " " + "--suppress=variableScope"
        check_suppress += " " + "--suppress=unreadVariable"
        check_suppress += " " + "--suppress=constParameter"
        check_suppress += " " + "--suppress=ConfigurationNotChecked"
        cmd = '%s --force --enable=all %s  --xml --output-file=%s %s %s' % (
                cppcheck_script, check_suppress, cppcheck_outputfile, cppcheck_path, cppcheck_file_filter)
        TIMEOUT_SHELL_COMMAND(cmd, timeout=180)
        # run cppcheck cmd
        if os.path.exists(reporthtml_report_path):
            cmd = 'rm -rf %s' % (reporthtml_report_path)
        cmd = '%s --file=%s --report-dir=%s --source-dir=.' % (
                reporthtml_script,
                cppcheck_outputfile,
                reporthtml_report_path.rstrip('/'))
        TIMEOUT_SHELL_COMMAND(cmd, timeout=180)

        gerrit_msg = '[cppcheck] FAIL  '
        if os.path.exists(cppcheck_outputfile) and os.path.exists(reporthtml_report_path):
            zip_name = reporthtml_report_path.rstrip('/').split()[-1]
            cmd = 'zip -r %s.zip %s' % (zip_name, reporthtml_report_path)
            ret = TIMEOUT_SHELL_COMMAND(cmd, cwd=_guardInfo.GetWorkingPath())
            if ret[0]:
                LOG(LEVEL_WARN, 'Build zip file fail [{}]'.format(cmd))

            gerrit_msg = '[cppcheck] PASS  '
            _guardInfo.cppcheck_success = True
            xml_data = XMLET.parse(cppcheck_outputfile)
            root = xml_data.getroot()
            for child in root:
                if (child.tag == 'errors') and (len(child.getchildren()) > 0):
                    gerrit_msg = '[cppcheck] FAIL  '
                    _guardInfo.cppcheck_success = False
                    break
        else:
            gerrit_msg = '[cppcheck] FAIL  '
    else:
        gerrit_msg = '[cppcheck] PASS  '
        _guardInfo.cppcheck_success = True

    if 'FAIL' in gerrit_msg:
        gerrit_msg += '\\\n\\\r'
        # http://192.168.29.22:8080/job/tws_guard_2/24/artifact/working/cppcheck_report/index.html
        gerrit_url = '{}/artifact/{}{}/index.html'.format(_guardInfo.jenkins_url, _WORKING_ROOT_DIR, reporthtml_report_dir)
        gerrit_msg += 'You can check the cppcheck_report on\\\n\\\r{}\\\n\\\r'.format(gerrit_url)
        gerrit_url = '{}/artifact/{}{}.zip'.format(_guardInfo.jenkins_url, _WORKING_ROOT_DIR, reporthtml_report_dir)
        gerrit_msg += 'Or you can download report on\\\n\\\r{}'.format(gerrit_url)
    _guardInfo.NotifyReviewToGerrit(gerrit_msg)


def _DeleteCode(path):
    if os.path.exists(path):
        cmd = 'rm -rf %s' % (path)
        subprocess.run(cmd, shell=True, timeout=120, encoding='utf8')


def DoReview(res_check=True, lint_check=True):
    # step x: Init log env
    _LogInit(workdir=_guardInfo.GetWorkingPath(), codedir=_guardInfo.GetCodeRootPath(), guard_info_handle = _guardInfo)
    # step 1: set project work dir
    _InitReviewerWorkingEnv()
    # step 2: pull the project orginal code
    _PullProjectCode()
    _guardInfo.ParseProjectManifestInfo()
    # step 3: build the orginal code and save the output file
    _BuildEarphone(_guardInfo.GetImagePath('earbud', 'org'))
    # step 4: cherry pick the patch
    _CherrypickThePatch()
    # step 5: build the patch and save the output file
    _BuildEarphone(_guardInfo.GetImagePath('earbud', 'patch'))
    # step 6: do resource check
    if res_check:
        _DoResourceCheck()
    # step 7: do tws lint check
    if lint_check:
        _DoTwsLintCheck()
    # step x: do cppcheck
    _DoCppcheck()
    # step 8: build related project
    _build_impact_project_earphone()
    # step 9: delete code
    _DeleteCode(_guardInfo.GetCodeRootPath())
    # step x: send gerrit review finished info
    # step x: exist with error status\
    if not _guardInfo.tws_lint_success or not _guardInfo.cppcheck_success or not _guardInfo.impact_build_success:
        # exit('tws_lint or cppcheck fail')
        LOG(LEVEL_ERROR, f'Review [Failed]: tws_lint [{_guardInfo.tws_lint_success}], cppcheck [{_guardInfo.cppcheck_success}], impact_build [{_guardInfo.impact_build_success}]')
    else:
        review_msg = ' Review [Successful] at {}'.format(_guardInfo.jenkins_url)
        _guardInfo.NotifyReviewToGerrit(review_msg, review=" --verified +1")


def GuardInit(output_type='offline', account_id='', change_id='', project_name='', jenkins_url=''):
    _guardInfo.output_type = output_type
    _guardInfo.account_id = account_id
    _guardInfo.change_id = change_id
    if jenkins_url:
        _guardInfo.jenkins_url = jenkins_url
    _guardInfo._SetProjectName(project_name)
    _QueryGerritInfo()
    _guardInfo.NotifyReviewToGerrit(' Review Start At: {}'.format(_guardInfo.GetJenkinsUrl()))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='')
    parser.add_argument('--type',
                        '-t',
                        required=True,
                        metavar='online|offline',
                        choices=['online', 'offline'],
                        help='Output the result to gerrit (online) or stdio (offline)')
    parser.add_argument('--account',
                        '-a',
                        required=True,
                        metavar='1010xxxxx',
                        help='The gerrit account ID set on the PC')
    parser.add_argument('--changeid',
                        '-c',
                        required=True,
                        metavar='ChangeID',
                        help='The gerrit change ID you want to check')
    parser.add_argument('--project',
                        '-p',
                        required=True,
                        metavar='ts6239|ts6351a|ts6569a|all',
                        help='The project name of the change, "all" for check all project we have')
    parser.add_argument('--jenkins_url',
                        '-u',
                        metavar='optional for jenkins url',
                        help='The jenkins url where this script runing')
    args = parser.parse_args()

    GuardInit(args.type, args.account, args.changeid, args.project, args.jenkins_url)
    DoReview()
